package com.demo.webapp.ui.displayer;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import javax.servlet.jsp.jstl.core.Config;

import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;

import com.demo.common.ClassLoaderUtils;
import com.demo.webapp.ui.Component;
import com.demo.webapp.ui.displayer.AbstractDisplayer;

/**
 * 带有消息资源的展示器对象
 * 提供对语言基础的展示器
 * 
 * @author ryuu.kk
 *
 */
public abstract class MessageResourcesDisplayer<T extends Component> extends AbstractDisplayer<T> {

	/**
	 * 默认多语言资源名
	 * 资源文件的命名：basename_language_country.properties
	 * 资源名称构成@see java.util.ResourceBundle 
	 */
	public static final String RESOURCES_BUNDLE_NAME = "globalUIMessage";
	/** 资源默认名称 **/
	private String baseName = RESOURCES_BUNDLE_NAME;
	
	private ClassLoader bundleClassLoader;
	
	private ClassLoader beanClassLoader = ClassLoaderUtils.getDefaultClassLoader();
	private Locale locale;
	protected Object messages = null;

	private final Map<String, Map<Locale, ResourceBundle>> cachedResourceBundles = new HashMap<String, Map<Locale, ResourceBundle>>();
	private final Map<ResourceBundle, Map<String, Map<Locale, MessageFormat>>> cachedBundleMessageFormats =	new HashMap<ResourceBundle, Map<String, Map<Locale, MessageFormat>>>();
	
	/**
	 * 使用默认的message本地化消息有一个线程同步问题
	 * 当因为displayer使用对象Map存储,则每个用户共享Map的displayer
	 * 当用户设置setLocale时,将会影响其他用户locale设置
	 * 这里使用spring的本地local做为用户选择的locale
	 * @see com.demo.webapp.filter.LocaleFilter
	 * @see org.springframework.context.i18n.LocaleContextHolder
	 */
	protected String beforeMessage(String code, Object[] args, Locale locale) {
		if (log.isDebugEnabled()) {
			log.debug("Look at " + code + " messages.");
		}
		return null;
	}
	
	/**
	 * 返回国际化消息
	 * @param key 消息Key
	 * @param args 参数数组,根据locale进行格式化
	 * @param locale Locale
	 * @return 消息
	 */
	public String getMessage(String key, Object[] args, Locale locale) {
		String message = "";
		
		//使用各实现类自己的message
		message = beforeMessage(key, args, locale);

		if (message != null && message.length() > 0) {
			return message;
		}
		//数据注入本地化资源
		if (messages != null) {
			if (messages instanceof ResourceBundle) {
				if (log.isDebugEnabled()) {
					log.debug("Looking up string '" + key + "' in ResourceBundle");
				}
				ResourceBundle bundle = (ResourceBundle) messages;
				try {
					message = bundle.getString(key);
				} catch (MissingResourceException mre) {
					message = null;
				}
			} else if (messages instanceof MessageSource) {
				//一个Spring's MessageSource 对象实例
				if (log.isDebugEnabled()) {
					log.debug("Looking up message '" + key + "' in Spring' MessageSource");
				}
				//使用Spring的MessageSource处理国际化消息,该对象由Spring容器管理。
				MessageSource messageSource = (MessageSource) messages;
				try {
					if (locale != null) {
						message = messageSource.getMessage(key, args, locale);
					} else {
						// 使用配置的默认Locale
						message = messageSource.getMessage(key, args, getLocale());
					}
				} catch (Throwable t) {
					message = null;
				}
			} else {
				//其他类型？
			}
			return message;
		}
		
		//不存在注入的messages对象,则使用使用ResourceBundle处理,并缓存
		//参照 ：@see org.springframework.context.support.ResourceBundleMessageSource
		if (args == null || args.length == 0) {
			message = resolveCodeWithoutArguments(key, locale);
			if (message != null) {
				return message;
			}
		} else {
			MessageFormat messageFormat = resolveCode(key, locale);
			if (messageFormat != null) {
				synchronized (messageFormat) {
					return messageFormat.format(args);
				}
			}
			message = key;
		}
		return message;
	}
	
	public String getMessage(String code) {
		return getMessage(code, null, locale);
	}
	
	public String getMessage(String code, Object... args) {
		return getMessage(code, args, locale);
	}

	public void setMessageBundle(Object messages) {
		this.messages = messages;
	}
	
	public void setBaseName(String baseName) {
		this.baseName = baseName;
	}
	
	public void setLocale(Locale locale) {
		this.locale = locale;
	}
	
	public Locale getLocale() {
		if (locale == null) {
			locale = LocaleContextHolder.getLocale();
		}
		return locale;
	}

	public void setBeanClassLoader(ClassLoader classLoader) {
		this.beanClassLoader = (classLoader != null ? classLoader : ClassLoaderUtils.getDefaultClassLoader());
	}

	protected ResourceBundle getResourceBundle(String basename, Locale locale) {
		synchronized (this.cachedResourceBundles) {
			Map<Locale, ResourceBundle> localeMap = this.cachedResourceBundles.get(basename);
			if (localeMap != null) {
				ResourceBundle bundle = localeMap.get(locale);
				if (bundle != null) {
					return bundle;
				}
			}
			try {
				ResourceBundle bundle = doGetBundle(basename, locale);
				if (localeMap == null) {
					localeMap = new HashMap<Locale, ResourceBundle>();
					this.cachedResourceBundles.put(basename, localeMap);
				}
				localeMap.put(locale, bundle);
				return bundle;
			} catch (MissingResourceException ex) {
				if (log.isWarnEnabled()) {
					log.warn("ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage());
				}
				return null;
			}
		}
	}
	protected ResourceBundle doGetBundle(String basename, Locale locale)
			throws MissingResourceException {
		return ResourceBundle.getBundle(basename, locale, getBundleClassLoader());
	}

	protected String resolveCodeWithoutArguments(String code, Locale locale) {
		MessageFormat messageFormat = resolveCode(code, locale);
		if (messageFormat != null) {
			synchronized (messageFormat) {
				return messageFormat.format(new Object[0]);
			}
		}
		return null;
	}

	/**
	 * Resolves the given message code as key in the registered resource bundles,
	 * using a cached MessageFormat instance per message code.
	 */
	protected MessageFormat resolveCode(String code, Locale locale) {
		MessageFormat messageFormat = null;
		ResourceBundle bundle = getResourceBundle(this.baseName, locale);
		if (bundle != null) {
			messageFormat = getMessageFormat(bundle, code, locale);
		}
		return messageFormat;
	}
	protected MessageFormat getMessageFormat(ResourceBundle bundle, String code, Locale locale)
			throws MissingResourceException {

		synchronized (this.cachedBundleMessageFormats) {
			Map<String, Map<Locale, MessageFormat>> codeMap = this.cachedBundleMessageFormats.get(bundle);
			Map<Locale, MessageFormat> localeMap = null;
			if (codeMap != null) {
				localeMap = codeMap.get(code);
				if (localeMap != null) {
					MessageFormat result = localeMap.get(locale);
					if (result != null) {
						return result;
					}
				}
			}

			String msg = getStringOrNull(bundle, code);
			if (msg != null) {
				if (codeMap == null) {
					codeMap = new HashMap<String, Map<Locale, MessageFormat>>();
					this.cachedBundleMessageFormats.put(bundle, codeMap);
				}
				if (localeMap == null) {
					localeMap = new HashMap<Locale, MessageFormat>();
					codeMap.put(code, localeMap);
				}
				MessageFormat result = createMessageFormat(msg, locale);
				localeMap.put(locale, result);
				return result;
			}

			return null;
		}
	}
	protected MessageFormat createMessageFormat(String msg, Locale locale) {
		return new MessageFormat((msg != null ? msg : ""), locale);
	}
	
	private String getStringOrNull(ResourceBundle bundle, String key) {
		try {
			return bundle.getString(key);
		}
		catch (MissingResourceException ex) {
			// Assume key not found
			// -> do NOT throw the exception to allow for checking parent message source.
			return null;
		}
	}

	protected ClassLoader getBundleClassLoader() {
		return (this.bundleClassLoader != null ? this.bundleClassLoader	: this.beanClassLoader);
	}
}
